package com.example.config;

import com.example.factory.YamlPropertySourceFactory;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;

/**
 * 模拟数据库配置文件。<p>
 * {@code @PropertySource}：该注释提供了一种方便的声明机制，可以用于将指定的配置文件也添加到 Spring 的Environment 。与Configuration类一起使用。<p>
 * {@code @ImportResource}：指示一个或多个包含要导入的 bean 定义的资源。此注解提供类似于 Spring XML 中的{@code <import/>}元素的功能。
 * 简单理解：将指定的配置文件导入当前配置，合成一个配置类<p>
 *
 * @author yexi
 */

@Configuration
@PropertySources({
        @PropertySource({"classpath:db.properties"}),
        @PropertySource(value = {"classpath:web.yml"}, factory = YamlPropertySourceFactory.class)
})
@ImportResource("classpath:application.xml")
public class DatabaseConfiguration {

    //@Value  资源文件取值注解
    @Value("${jdbc.driverClassName}")
    private String driverClassName;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password:admin}")
    private String password;

    @Setter
    @Getter
    @ToString
    public static class MyDataSource {
        private String driverClassName;
        private String url;
        private String username;
        private String password;

        public MyDataSource() {
            System.out.println("MyDataSource的无参构造器被调用: " + this);
        }

        public void init() {
            System.out.println("MyDataSource的init方法被成功调用");
        }

        public void destroy() {
            System.out.println("MyDataSource的destroy方法被成功调用");
        }
    }

    /**
     * {@code @Bean}：
     * 1、贴有该注解的方法，表示该方法的返回值将成为一个交由 Spring 容器管理的 bean。与配置文件中的{@code <bean/>}元素的名称和语义相似。
     * 2、此时原本{@code <bean/>}中的id和name默认策略是使用方法名，但是如果显式地定义了@Bean中的name，则会使用提供的名称覆盖掉方法名。
     * 注意的是，name属性是个数组，这意味着一个bean可以存在多个别名。
     * 3、通常， @Bean方法在@Configuration类中声明。在这种情况下，bean 方法可以通过直接调用它们来引用同一类中的其他@Bean方法。
     * 这确保了 bean 之间的引用是强类型(strongly typed)和可导航的(navigable)。这种所谓的"bean之间引用(inter-bean references)"保证关联范围和 AOP 语义，就像getBean()查找一样。
     * 这些是原始"Spring JavaConfig"项目中已知的语义，该项目要求在运行时对每个此类配置类进行 CGLIB 子类化。因此， @Configuration类及其工厂方法在此模式下不得标记为 final 或 private。
     * 4、@Bean方法也可以在未使用@Configuration注释的类中声明。例如，bean 方法可以在@Component类中声明，甚至可以在普通的旧类中声明。在这种情况下， @Bean方法将以所谓的"精简(lite)"模式进行处理。
     * 精简模式下的 Bean 方法将被容器视为普通工厂方法（类似于 XML 中的factory-method声明），并适当地应用范围(scoping)和生命周期回调。在这种情况下，包含类(containing class)保持不变，并且包含类或工厂方法没有异常约束。
     * 与@Configuration类中 bean 方法的语义相比，精简模式不支持"bean之间引用"。相反，当一个@Bean方法在精简模式下调用另一个@Bean方法时，调用是标准的 Java 方法调用；
     * Spring 不会通过 CGLIB 代理拦截调用。这类似于在代理模式下的inter-@Transactional方法调用，Spring 不会拦截调用——Spring 仅在 AspectJ 模式下这样做。
     * 5、spring对于不同注解从而创建bean的解析与创建时间是不一致的，而若两bean的名称发生重复时，会用新的覆盖旧的（这点与使用xml时直接报错不同）。
     */
    @Bean(name = {"myDataSource", "customDataSource"}, initMethod = "init", destroyMethod = "destroy")
    public MyDataSource myDataSource1() {
        MyDataSource source = new MyDataSource();
        source.setDriverClassName(driverClassName);
        source.setUrl(url);
        source.setUsername(username);
        source.setPassword(password);
        return source;
    }

    //spring加载资源文件之后，会将文件解析全部放置到这个对象里面
    @Autowired
    private Environment evn;

    @Bean
    public MyDataSource myDataSource2() {
        MyDataSource source = new MyDataSource();
        source.setDriverClassName(evn.getProperty("jdbc.driverClassName"));
        source.setPassword(evn.getProperty("jdbc.password"));
        source.setUrl(evn.getProperty("jdbc.url"));
        source.setUsername(evn.getProperty("jdbc.username"));
        return source;
    }
}